Module Syntax and Usage

The Basics

A listing of all of the valid commands and operations that can be used in the module system is found in the file header_operations.py. It includes comments (following the # sign) that may give a hint as to each operations use, but also identify what arguments need to be included with each operation. Every operation used in the module system takes the following form:

  • It is enclosed in parentheses: (operation)
  • It is immediately followed by a comma: (operation),
  • Arguments within the operation parentheses are separated by a comma: (operation, argument, argument)

Additionally, the header_operations.py file assigns a numerical code to each of the operations listed there. For instance, call_script = 1. When the game gives errors, they are in the form of SCRIPT ERROR ON OPCODE ##:..., the provided OPCODE number refers to the number assigned to the operation in header_operations.py. So, if the error was with OPCODE1, the line of code in question used the operation call_script.

Items contained within the various module files are indexed, that is assigned an ordinal numeric value based on the order they are found in the file. These indexes begin with 0 and count by 1 to each next value. Thus, troops, items, triggers, scenes, etc, once compiled into .txt files, are referred to by their index value. So, if you see an error code in "'trigger 0"', that is the first trigger in the indicated file, and if the error is in line 4, you are looking for the fifth operation within that trigger.

Variables

Local variables - ":variable" - variables declared with a : are local variables. They only exist in the current code block, enclosed by brackets [ ], and cannot be referenced in other code blocks unless passed as script parameters. One should initialize them (to 0 or another suitable value) before they are used as they may contain a value already.[1] They are enclosed with quotations " ". Ex: ":agent_id"

Global variables - "$variable" - variables declared with a $ are global variables. They are available to be referenced in any part of the code, from any module_*, once they are defined. Their default value is 0. They are enclosed in quotations " ". Ex: "$g_talk_troop".[2]

Registers - reg(#) OR reg# - are variables used by the engine to store bits of information temporarily. They are global in scope, but may be accessed by a variety of different scripts for their duration and are then left for another bit of code to use them. In header_common.py the registers reg0 to reg65 are declared.[3]

Strings - s# - are specific 'registers' used to hold a string, rather than an integer. Strings not declared in module_strings.py (quick strings) begin with the @ symbol. In header_common.py the strings s0 to s67 are declared.

Positions - pos# - Unlike the previous two "registers", positions store more than one piece of information; each position stores X, Y, Z coordinates as well as rotations on each X, Y, and Z axis. In header_common.py the positions pos0 to pos64 are declared, with pos64 beginning an unspecified range of positions used by siege towers (belfries).

Constants - constant - constants are declared in module_constants.py, maintain the numerical value assigned there, and cannot be altered save through editing module_constants.py. They can be called from any module_* file and any code block. Constants are not enclosed with quotations.

Local and global variables, as well as registers, are declared with the operation (assign, <variable_name>, <variable_value>),
The variable value field can be another variable name. Strings and positions have their own unique operations, as defined in header_operations.py.

Keep in mind that registers and variables can only be used where they are meant to go: in conditions and operations blocks.[4] The biggest value a variable can hold is 2^53, based on the assumption that the bit 54 (of the below quoted lines in header_common.py) is a sign bit:[5]

								
									op_num_value_bits = 24 + 32
...
opmask_register = tag_register << op_num_value_bits

Uninitiated Variables[6]

Variables in the Module System don't need to be created with the assign operation. They share however their memory address and can show a weired behaviour in some cases (like fast loops) or OS (like Linux and Mac).

The game engine will overflow uninitiated variables at the operation systems Linux and Macs. If you ever saw a budget report with trillions of coins of deficit, that is why. In most cases the variables get thus assigned the values -1 or 0 in the Module System, even if something else is going to be stored in them a few lines later. Global variables are different from local variables, as they are stored on the savegame (as slots) and thus always have a defined value (including a default initial value).

The engine does also not handle loops well. So it is not uncommon to see cases of ghost values causing unpredictable bugs. For those cases your best bet is to make sure to not depend on defaults. That is mostly for local variables (which use the same register in memory).

Module System Prefixes

Within the Module System (eg module_mission_templates.py), one can refer to code or information stored in other files of the Module System by using a prefix before the label for the piece of code. Only module_dialogs.py has no prefix since it is never directly referenced.

module_animations: "anim_" module_pstfx: "pfx_"
module_factions: "fac_" module_presentations: "prsnt_"
module_info_pages: "ip_" module_quests: "qst_"
module_items: "itm_" module_scene_props: "spr_"
module_game_menus: "menu_" module_scenes: "scn_"
module_map_icons: "icon_" module_scripts: "script_"
module_meshes: "mesh_" module_skills: "skl_"
module_mission_templates: "mst_" module_sounds: "snd_"
module_music: "track_" module_strings: "str_"
module_particle_systems: "psys_" module_tableau_materials: "tableau_"
module_parties: "p_" module_troops: "trp_"

You might see that such an ID-variable is at some places referenced with quotation marks and at others without, so for example "trp_swadian_levy_1" and trp_swadian_levy_1. The quptes prevent you from needing to import from ID_troops when you use the reference (see Interactions between Module System Files). You use quoted strings for identifiers in module_*.py files that are set up to search for the identifier string and convert it to a number afterwards (like module_scripts.py), and use the unquoted variable names in certain other files where the processing script doesn't convert strings to numbers (like module_troops.py). Using the unquoted variable names has various drawbacks, like not being able to refer to the type of object that the current file contains, having circular dependencies or things getting messed up after removing an entry requiring you to build the module twice or even create the correct ID_*.py in certain situations.[7]

Module Scripts, Parameters, Arguments, etc.

Most of the module_* files contain self-contained code for the most part. Files such as _skills, _troops, _items etc. simply define these various elements for the game. module_game_menus contains all of the various menus, their options etc. Presentations has all of the more complicated presentation-drawing data, etc.

But module_scripts.py is referenced from many of these files - it contains "'common"' functions to be called upon by every part of the game code. You can consider (call_script, "script_scriptname", <optional_aruments>), a Goto:, Jump, function call, what have you.

To facilitate this process, the call script operation allows information to be passed from the current code to the script without using global variables. These arguments or parameters can be local variables generated by the current bit of code. The script code in module_scripts.py will then begin by storing the parameters passed to it into local variables for use throughout the script. Scripts cannot pass information back to the code they were called from in the same way though. To do this, registers are typically used. In the calling code, once the script has completed, these registers are recorded to local variables for ease of use. Up to 15 script parameters can be passed with any one call.

								
									//...statements...//
(assign, ":local_variable_1", 10),
(assign, ":local_variable_2", 5),
(call_script, "script_example_script", ":local_variable_1", ":local_variable_2"),
(assign, ":local_variable_3", reg0), #local_variable_3 = 15
//...further stuff, working with local_variable_3...//
								
									# script_example_script
# Input: variable_1, variable_2
# Output: Sum in reg0
("example_script", [
    (store_script_param, ":num1", 1), #now num1 is 10
    (store_script_param, ":num2", 2), #now num2 is 5
    (store_add, ":sum", ":num1", ":num2"),
    (assign, reg0, ":sum"),
]),
  • Remarks and Sources:
  • [1] If you have a script that is called repeatedly (from a loop) the local variables within this script are NOT reset with every call due to the quick succession of the script calls. One must initialize them at the beginning of the script so they are re-set with every call. Caba`drin, Modding Q&A.
  • [2] Global variables values are saved with saved games and a global variable declared in the start game script won't be redeclared when a game is loaded. Caba`drin, Modding Q&A.
  • [3] reg(#) is the old way which is still working but got replaced by reg# on newer versions of the Module System. You can can still see some leftovers on the Native Module System that were not updated; kalarhan, Modding Q&A.
  • [4] Yoshiboy, Modding Q&A.
  • [5] dunde, Modding Q&A.
  • [6] kalarhan, Modding Q&A, Modding Q&A and Modding Q&A.
  • [7] Caba`drin, Modding Q&A, and Vornne, Modding Q&A.

Terminology


Tuple

A tuple is a set of data fields surrounded by normal brackets. Every operation or structure definition in Module System is a tuple. Take module_scripts.py for example:

								
									scripts = [
  ("script_name",
    [
      (operation, ":param1", ":param2"),
      (another_operation, ":param1", ":param2", ":param3"),
    ]
  ),
  ("another_script_name",
    [
      (operation, ":param1", ":param2"),
      (another_operation, ":param1", ":param2", ":param3"),
    ]
  ),
]

What we have here is the array (square brackets, "scripts=[]" which contains two tuples (surrounded by normal brackets). Each of those tuples is a script definition. Each script in turn contains one string value (name of the script) and an array of operations. Each operation is once again a tuple, with first data value being the operation name, and the rest are operation parameters. So we have tuples inside array inside tuple inside another array. Simple eh? Especially when compared to more complex files like module_presentations.py.[8]

Troop vs Agent

Troops refer to the entries in module_troops.py by the name assigned to them there. "trp_player" is the player, "trp_npc##" for the various companions, "trp_knight_#_##" for lords, and "trp_swadian_footman" etc, etc. With more generic soldiers, the entry in module_troops.py is used as a "template" to create multiple instances of them in each party, etc.

Alternatively, agents refer to the specific actors, by number, in a scene (whether that scene is a battle, a tavern or what have you). Agents are created from the templates in module_troops.py, for the player, companions, NPC lords. They are still unique but with generic soldiers multiple agents can be created from a single troop template. Each agent has a unique number for that scene but when the scene ends the agent doesn't exist any more so its agent id is meaningless.

Parties and Party Templates

A "party" is any entity on the world map, be that the player's party, bandits and their lairs, NPC lords, or a town/castle/village. The different types of parties are separated into categories in the first party slot "slot_party_type" (or party slot 0, see the discussion of slots below), which contains an integer associated with a certain type. These types are defined in module_constants.py and begin with the heading spt_* (for slot_party_type). Some examples of spt_* values are spt_kingdom_hero_party (for NPC lords) and spt_castle (...for castles).

Each party has an ID number that it is referenced by in the code. For instance, the player's party is "p_main_party" which has always an index value or Party ID of 0. Certain parties have constant or static ID numbers, such as the player's party and all towns, castles and villages. Those parties with static IDs are defined in module_parties.py. You can see their ID number in the file ID_parties.py after you compile your module.

For all parties NOT defined in module_parties.py, those are created, dynamically, by the game based on a party template, in a manner somewhat parallel to the agent/troop division above. These party templates are defined in module_party_templates.py and have names that are referenced with the prefix "pt_#". The template contains, primarily, a list of troops that are contained in that template and a numeric range that is used to determine how many of each troop type will be given to that template in game (the engine randomly picks a number within that range). A party template can be used to create a new party with the command <spawn_party>. This new party is assigned an ID number and then can be classified with a slot_party_type value, etc. Party templates can also be used with already existing parties. One can add a template (the troops defined in that template) to another party to "reinforce" an existing party in this way.

Slots [9]

Slots are essentially ways to have variables arrayed by an object (party, troop, agent, team, faction, scene, item, player, scene prop, or party template). Each party, troop, agent, item etc has a number of "slots" (variables) assigned to it. That variable has a common name "slot_item_SLOTNAME" for each version of the object is assigned to (items in this case), which is useful, but stores unique information for each object. Where it looks like a "constant" is the fact that the engine turns the name of the arrayed variable (the slot) into a number in module_constants.py, for example "slot_item_base_price = 53". This means that the engine looks at the 53rd slot-variable associated with a given item to find that item's base price.

So, when you see operations using slots such as

								
									(item_get_slot, ":base_price", ":item_id", slot_item_base_price),
								
							

you can see that you need to specify exactly which item you want to get the base price for. But, since the variable is arrayed, you could embed that in a try_for_* loop and iterate through many ":item_id"'s to do things to the base price of every item, say.

You can access the price for a specific item with the item_get_slot operation, change it via item_set_slot, and test for equality with item_slot_eq and the like.

If you know C-like programming languages, slots work a lot like this:

								
									slot_troop_spouse
spouse[troop1] = spousename
spouse[troop2] = spousename
spouse[trp_player] = spousename

which the engine reads as troop_variable_30[troop1], troop_variable30[trp_player] etc. since the slot slot_troop_spouse is set as slot 30 in constants.

								
									slot_item_base_price
baseprice[item1] = priceA
baseprice[item2] = priceB
baseprice[item3] = priceC

and the engine reads that as item_variable_53[item1], item_variable_53[item2] etc. since it is set as 53 in constants.

Before being set with a *_set_slot operation, the value of all slots is 0 like for a global variable.[10] Each slot has unique storage for a large number; each slot number can store a 64 bit integer, but other parts of the game engine (like multiplayer network messages) can only deal with 32 bit integers, so you should keep within the -2147483648 to 2147483647 range to be safe. For each type of object you can use slot numbers (the ones defined in module_constants.py) from 0 to 1048575 (2^20), but you should keep the slot numbers fairly close together to avoid wasted memory usage - the engine allocates a continuous array of slots up to the highest number used.[11] If you try to use slot higher than the upper bound, it won't be assigned, and retrieving its value will always give 0.[12]

For instance, troop slots define what lords personalities are since they are unique for each lord. Agent slots define which agents are running away since it's also unique behavior for each agent and using global variables for this just won't do the job. Once you understand how slots work, you will always choose them over globals in some situations, not only because there is no other way, but sometimes because the feature will work better this way. Three things to remember when using slots:
  1. They can be defined in any file the are used in, not just module_constants. Make sure though that other files in which this slot is in have the file with definition included on the top with *. Or they could not be defined at all: if you are sure that you will remember their meaning you can use (agent_set_slot, ":agent", <slot_no>, <value>) instead of (agent_set_slot, ":agent", <slot_name>, <value>)|. This is actually true for any constant, not just slots.
  2. You can have multiple slots with the same number but make sure they are different "category". I.e. it is fine to have troop slot 145 and party slot 145 but avoid using two troop slots with number 145.
  3. Slot names are not saved anywhere during compilation, so they can be used to make your code more difficult to understand in compiled form (if that is what's you are after). When someone looks on code of your compiled module, to understand a global sometimes it's enough to look at its name. But to understand a compiled slot, you should look up all its uses and make conclusions based on them so it's far more difficult.

Should you face slots with apparently wrong naming, like slto_kingdom_hero for example, take note that slto stands for slot_troop_occupation, marking the constant values for these slots. So it is not a spelling mistake which needs to be corrected, merely a naming convention of TaleWorlds.[13]

Work in:Slots are getting reset to zero after reload module data, Somebody, Working with dialogs is frustrating

Remaining stuff: Not sure how to work in phantom values in slots (MadVader). There are also different comments with varying values (_Sebastian_ and _Sebastian_) and some most probably outdated informations which might be interesting if still valid (Hellequin and Hellequin). Comment of cmpxchg8b about game engine dealing with slots (cmpxchg8b)

Fixed Point Values/Numbers

Mathematical operations deal with numbers. The Module System can only deal with integers. Floating point numbers are emulated by the so-called "fixed point numbers". Wherever you encounter a fixed point parameter for some Module System operation, keep in mind that it is actually just a regular integer number. HOWEVER it is supposed to represent a floating point number equal to fixed_point_number / fixed_point_multiplier. As you might have guessed, to convert a floating point number to fixed point, you have to multiply it by fixed_point_multiplier. You can change the value of the multiplier with the operation set_fixed_point_multiplier, thus influencing the precision of all operations dealing with fixed point numbers.

To summarise it shortly, with an example afterwards: A fixed point number simply multiplies the result <destination_fixed_point> with the given fixed point multiplier, allowing for more precise calculations. Keep in mind that <value_fixed_point> has to be multiplied by the fixed point value before using the specific math operation. Lets say you want to find out the square root of 123 and the result should be accurate to 3 decimal places:

								
									(assign, ":value", 123),
(val_mul, ":value", 1000),
(set_fixed_point_multiplier, 1000),
(store_sqrt, ":result", ":value"),

The variable ":result" would have the value 11090.[14]

Control and Conditional Operations and Syntax

In general, it is imperative to note that the game engine treats every conditional operation as an "IF" and all of the code that follows after a conditional operation as the "THEN" in an If-Then statement. As the engine goes through lines of code, it will stop any time a conditional operation fails, and it will not read the remainder of the code.

To isolate If-Then(-Else) statements to allow failure but continue processing the remainder of the code, one must use a "try block" with (try_begin), (else_try), and (try_end), This is discussed in more detail below.

List of Conditional Operations

								
									(eq,<value>,<value>),  Does Value 1 = Value 2?
(gt,<value>,<value>), Is Value 1 > Value 2?
(ge,<value>,<value>), Is Value 1 >= Value 2?
(lt,<value>,<value>), Is Value 1 < Value 2?
(le,<value>,<value>), Is Value 1 <= Value 2?
(neq,<value>,<value>), Does Value 1 != Value 2?
(is_between,<value>,<lower_bound>,<upper_bound>), Is Lower <= Value < Upper?

Also, any operation that includes "_is_" can be considered a true-false condition test. For instance, (agent_is_alive,<agent_id>), will do a conditional test to check if the agent ID provided is alive. If the test is true, the code continues. If it is false, the code stops.

To test for "FALSE" rather than "TRUE" in any "_is_" T/F conditional test, use the following:

								
									(neg|<operation>),
								
							

From our previous example - (neg|agent_is_alive,<agent_id>), - the code will only continue if the agent identified by the ID number is dead (NOT alive). Take note that neg is for the opposite/inverse of the operation while neq stands for "not equal", they are not the same!

List of Control Operations

								
									(try_begin),
(try_end),
(else_try),
(try_for_range,<destination>,<lower_bound>,<upper_bound>),
(try_for_range_backwards,<destination>,<lower_bound>,<upper_bound>),
(try_for_parties,<destination>),
(try_for_agents,<destination>),

Destination is the variable that will be iterated in a loop. The iteration will begin at the lower bound and go to Upper Bound -1.

Boolean AND and OR

Since, as discussed above, the engine treats every conditional operation as the "IF" in an If-Then and all the code beneath it as the "THEN", the boolean AND can be accomplished simply by stringing multiple conditional operations after one another.

For instance, if one wanted to locate an agent who is alive AND a horse AND an enemy, the code would simply be:

								
									(agent_is_alive,<agent_id>),
(neg|agent_is_human,<agent_id>),
(neg|agent_is_ally,<agent_id>),

To check for one condition OR another, (this_or_next|<operation>), is used. By our previous logic, it can be strung together multiple times as well. For instance, to see if a variable is 1 OR 5 and, if so, continue:

								
									(this_or_next|eq, ":test_variable", 1),
(eq, ":test_variable", 5),

If-Then-Else

Since all condition operations apply to every line of code beneath them, there needs to be a way to separate portions of code so the condition operation only applies to one section and then the code continues on regardless of the result of the test.

To do this, one uses a "try block", code enclosed by (try_begin) and (try_end). One can think of them as creating a typical If-Then statement, with the first lines of code after the (try_begin) being the "If" in the form of conditions tests and the following lines being the "Then" with consequence operations, which are closed by the "End If" of (try_end).

As in all good If-Thens, (else_try) emerges as the "Else If" statement. At any point that a condition fails in the try before an (else_try) the engine will begin looking in the subsequent (else_try). To give an example:

								
									#Code above, doing whatever
(try_begin),
    (eq, 1, 1), #True, go to next line
    (gt, 2, 5), #False, go to else try
    #consequence operations here
(else_try),
    (eq, 0, 1), #False, go to next else try
    #consequence operations here
(else_try),
    (eq, ":favorite_lance", ":jousting"), # Maybe?
    (assign, ":prisoners", 100),
(try_end),
#Code continuing on, regardless of whether the favorite lance=jousting and prisoners was set to 100

Conjunctive and Disjunctive Normal Form [14]

Stringing multiple conditional operations after one another can sometimes lead to quite complex checks which evaluate eventually in a conjunctive normal form (CNF) or a disjunctive normal form (DNF). A CNF is for those who know mathematical logic a conjunction of disjunctions, a stringing like if (a or x) and (b or y) and (c or z). Such one can easily be expressed in MABL as follows:

								
									(this_or_next|eq,"$variable_a",1),(eq,"$variable_x",1),
(this_or_next|eq,"$variable_b",1),(eq,"$variable_y",1),
(this_or_next|eq,"$variable_c",1),(eq,"$variable_z",1),

If you want to express a stringing of conditional operations like if (a and x) or (b and y) or (c and z) then you want to express a DNF. There are two different ways to achieve such via MABL. The more intuitive way, depending on your familiarity with logical expressions, is longer and uses more registers as can be seen here:

								
									(try_begin),
    (eq,"$variable_a",1),(eq,"$variable_x",1),
    (assign,reg(1),1),
(try_end),
(try_begin),
    (eq,"$variable_b",1),(eq,"$variable_y",1),
    (assign,reg(2),1),
(try_end),
(try_begin),
    (eq,"$variable_c",1),(eq,"$variable_z",1),
    (assign,reg(3),1),
(try_end),
    (this_or_next|eq,reg(1),1),
    (this_or_next|eq,reg(2),1),
    (eq,reg(3),1),
#block continues with whatever you want

The alternative approach would be to convert the DNF to a negation of a CNF using the De Morgan's laws. After the conversion the above wanted string of conditional operations would thus be if ~( (~a or ~x) and (~b or ~y) and (~c or ~z) ) then. The negated CNF is then expressible in MABL as follows:

								
									(try_begin),
    (this_or_next|neq, "$a",1),(neq, "$x",1),
    (this_or_next|neq, "$b",1),(neq, "$y",1),
    (this_or_next|neq, "$c",1),(neq, "$z",1),
(else_try),
    (do stuff here),
(try_end),

It might not be immediately obvious to you that the negated CNF is in fact equivalent to the DNF you want. Just take your time to think about it.

For-Next Loop

The M&B Module System has a number of different variations of the For-Next loop. The most basic is accomplished with a (try_for_range, ":iterator", <lower bound>, <upper bound>), then looped operations and capped by a (try_end), instead of a "Next iterator" or what have you.

The iteration will begin at the lower bound and go to upper bound -1, making single integer steps, counted by the variable assigned as destination, in the example above the local variable ":iterator". The ":iterator" must be used in the code that follows in the try_for_range block, but it cannot be altered to skip integer-step iterations toward upper bound -1. If the ":iterator" is not used in the code that follows the variable, ":unused_iterator" MUST be used in its place.[15] Otherwise loads of warnings about the unused iterator variable will come up. To give an example:

								
									(try_for_range, ":i", 0, 10),
    (store_add, ":count", ":i", 1),
    (assign, reg0, ":count"),
    (display_message, "@{reg0}"),
(try_end),
#This code will display a message on screen counting from 1 to 10.

There is also (try_for_range_backwards, ":iterator", <lower bound>, <upper bound>), where the iteration will begin at upper bound -1 and count down to lower bound. Ignore the comments in header_operations that say to switch the order of the lower and upper bounds. They are wrong. This is useful for removing something from a list (members from a party, for instance) without messing up the indexing of that list.

Note, that both the lower bound and the upper bound need not be a "plain number"; they can easily be variables that are set earlier in the code. Examples of this in the next section.

Two more specific (try_for_*) loops exist: (try_for_agents, <agent_id>), and (try_for_parties, <party_id>),. These will cycle through all agents in a scene or all parties in the game, respectively. The iterator represents the Agent's (or Party's) ID number and should be stored in a local variable. To give an example:

								
									(get_player_agent_no, ":player"),
(agent_get_team, ":playerteam", ":player"),
(try_for_agents, ":agent"), #Loops through all agents
    (agent_is_alive, ":agent"), #Checks if current agent is alive
    (agent_is_human, ":agent"), #If alive, checks if current agent is human (not a horse)
    (agent_is_non_player, ":agent"), #If alive and human, checks that the current agent isn't the player
    (agent_get_team, ":team", ":agent"), #If alive, human and non-player, gets the current agent's team.
    (eq, ":team", ":playerteam"), #Checks that the current alive, human, non-player agent is on the player's team.
    #Consequence operations go here to do stuff with this alive, human, non player, ally agent.
(try_end), #Go to next agent

Loop Breaks

Many times you may want to loop through all agents to locate one particular agent, or loop through another range of things (items for instance) to locate one particular weapon, and there is no need to loop through the rest - you've already found what you needed. Enter the loop break. The M&B Module System does not have any operations specifically to accomplish this, but there are a few simple tricks to effectively break a loop. The trick to use depends on the type of Module System (try_for_*) loop you are using.

    Method 1: Changing the Loop's End
    - Breaking a try_for_range loop:

You can break a try_for_range loop easily by changing the upper bound so it equals the lower bound. That way, when the engine tries to iterate to the next instance of the loop, it appears the loop has already reached the last iteration and is complete. The loop will exit without running the code again.

To do this, the loop's upper bound must be a variable defined before the loop begins, and it must be variable that can be modified without loss of data - make a new variable or a copy of another if necessary. Once the code has been executed enough times, something has been found, etc, set the variable for the end of the loop to equal the loop's lower bound (either assign it the variable value or the constant value that the loop began with). Example:

								
									#Within a larger (try_for_agents) loop
    (assign, ":end", "itm_glaive"), #Set the loop's upper bound
    (try_for_range, ":item", "itm_jousting_lance",":end"), #Loop through the lances in the game
        (agent_has_item_equipped, ":agent", ":item"), #Check if the agent has the current lance equipped
        (agent_set_wielded_item, ":agent", ":item"), #If the current lance is equipped, make the current agent wield it
        (assign, ":end", "itm_jousting_lance"), #loop breaker - make the upper bound equal the lower bound - stop looking through the lances
    (try_end),
#Continue on within the try_for_agents loop
    - Breaking a try_for_range_backwards loop:

The same method is used for a backwards loop. This time, however, the lower bound is the "loop's end" so the lower bound will need to be the variable changed, and it should be changed to equal the upper bound. Example:

								
									#Code above...sets the variable ":troop" to some value
(assign, ":array_begin", 0), #Set the loop's lower bound
(try_for_range_backwards, ":i", ":array_begin", 10), #Loop from 0 to 10
    (party_slot_eq, "p_main_party_backup", ":i", 0), #Check if the ith party slot for the player's party backup equals 0
    (party_set_slot, "p_main_party_backup", ":i", ":troop"), #If it does, set that slot to the variable troop's value
    (assign, ":array_begin", 10), #Loop breaker - make lower bound equal the upper bound - don't check other slots
(try_end),
    Method 2: Using an Condition Test
    - Breaking a try_for_agents or _parties loop

With try_for_agents and try_for_parties loops, it is not possible to change the loop's "upper bound". Instead, a conditional test (typically for equality) is used at the start of the code to execute within the loop. So long as it is true, the code within the loop will run. Once it is set false, the loop will keep going, but nothing inside the loop will happen since the first condition always fails - the loop will end quickly.

To do this, set a variable up before the loop with a given value (it is easiest to create a new variable with the value 0). Then, just inside the loop, set up an conditional operation testing for that value. After the block of code you want only ran once, change the value of the variable so the condition test fails the next time the loop runs. Example:

								
									#Code above...sets pos1 to some value
(assign, ":break_loop", 0), #Set up variable to break loop and a value to test
(try_for_agents, ":agent"), #Loop through all agents
    (eq, ":break_loop", 0), #See if the loop-breaker is still true
    (agent_is_alive, ":agent"), #If so, keep going; check if the current agent is alive
    (agent_is_non_player, ":agent"), #If alive, check it isn't the player
    (agent_is_human, ":agent"), #If alive and not the player, check that is is human
    (agent_get_position, pos0, ":agent"), #If alive, human and non-player, get it's location and record to pos0
    (get_distance_between_positions_in_meters, ":distance_to_target_pos", pos0, pos1), #See how far pos0 is from pos1 and record that in the local variable
    (lt, ":distance_to_target_pos", 10), #Check if the alive, human non-player agent is within 10 meters of pos1
    (assign, ":agent_at_target", ":agent"), #If so, record this current agent's ID to the local variable "agent_at_target"
    (assign, ":break_loop", 1), #Agent Loop Breaker - change the test variable so the equality test fails on the next try
(try_end),
#The loop will break the first time an alive, human, non-player agent is found within 10 meters of the target position pos1
#Further code can now do stuff with the agent that was found

Loop Extension

In a similar way like you can break a loop if a specific conditions is met you can also extend a loop if a specific condition is (not) met. See as an example the following script:

								
									(store_add,":range_end",towns_begin,1), # ":range_end" = towns_begin+1
(try_for_range,":town",towns_begin,":range_end"), # try for towns_begin to towns_begin+1
    (eq,":town",":found"), # if condition is true . . .
    # more here
(else_try),
    (neq,":town",":found"), # if condition is not true . . .
    (val_add,":range_end",1), # ":range_end" = ":range_end"+1, i.e. continue the try_for_range
(try_end),

This loop basically keeps adding 1 to the upper bound of the try_for_range} if the conditions aren't met.[16]

Indentation

Write some lines about it here. Declarations outside body can't be tabbed, Python indentation matters and VC auto indentation (kalarhan)